Фінальний проєкт з предмету "Базова робота з даними"¶

Цей проєкт виконує

In [ ]:
Вікторія Семенко

Обрані теми дослідження:

У своєму фінальному проєкті я зосередилася на аналізі змін у демографічній структурі населення села Дунаєць упродовж понад ста років, використовуючи два історичні джерела - перепис 1778 року та Всеросійський перепис 1897 року

Мої теми дослідження:

  • Вікова структура населення у селі Дунаєць між 1778 та 1897 роками
  • Зміни у структурі родин у селі Дунаєць між 1778 та 1897 роками
  • Зміни у статево-віковій структурі населення села Дунаєць у 1778 та 1897 роках

Я обрала саме ці теми, бо вони дозволяють побачити глибокі соціальні зміни у селі: чи збільшилася тривалість життя, як змінився розподіл за віком, наскільки великі були родини тощо. Також ці теми найкраще ілюструються за допомогою графіків

Підготовка до аналізуу

Спочатку імпортуємо основні бібліотеки Python, які потрібні для аналізу:

In [23]:
import pandas as pd
import matplotlib.pyplot as plt
import numpy as np

ТЕМА 1: Вікова структура населення у селі Дунаєць між 1778 та 1897 роками¶

Ця тема допоможе зрозуміти, як змінювалась демографічна картина у селі Дунаєць протягом 119 років: чи змінився загальний розподіл за віком, чи зросла кількість дітей або людей похилого віку, та які вікові групи були найчисельнішими.

Для зручності задамо для цієї теми такі кольори до кожного року

Це допоможе краще сприймати графіки та не плутати дані з різних років

In [24]:
COLORS = {
    1778: '#F56C78',
    1897: '#6795F2'
}

Завантажуємо дані для цієї теми - нам знадобляться ці дві таблиці

In [25]:
df_1897 = pd.read_csv('2.1_Дунаєць.xlsx - База людей.csv')
df_1778 = pd.read_csv("2.2_Дунаєць_1778.xlsx - База.csv", skiprows=3)

# Обрана таблиця містить дані по всьому населенню села — 
# це важливо, адже ми хочемо порівняти усю вікову структуру

Підтема 1: Порівняння загального розподілу віку населення у 1778 та 1897 роках¶

Обробка даних перепису 1897 року¶

У цій комірці завантажуються дані з перепису 1897 року. Оскільки у колонці 'Возраст' деякі значення вказані у місяцях (наприклад, '2 month'), ми замінюємо їх на умовне значення 0, яке інтерпретується як 'менше 1 року'.

Далі:

  • Перетворюємо вік на числовий тип
  • Залишаємо лише необхідні колонки: вік та стать
  • Додаємо колонку year зі значенням 1897, для зручності обʼєднання таблиць
In [26]:
df_1897['Возраст'] = df_1897['Возраст'].astype(str)

df_1897.loc[df_1897["Возраст"].str.contains('month', case=False, na=False), 'Возраст'] = '0'

df_1897['Возраст'] = pd.to_numeric(df_1897['Возраст'], errors='coerce')

df_1897 = df_1897[['Возраст', 'Пол']].dropna()
df_1897 = df_1897.rename(columns={
    'Возраст': 'Вік', 
    'Пол': 'Стать'
})

df_1897["year"] = 1897

df_1897.head()
Out[26]:
Вік Стать year
0 32.0 m 1897
1 28.0 f 1897
2 5.0 f 1897
3 1.0 m 1897
4 70.0 m 1897
Обробка даних перепису 1778 року¶

У цьому блоці обробляються дані зі сповідного розпису 1778 року

Особливість цієї таблиці - відсутність окремої колонки 'стать' Щоб визначити її, ми використовуємо колонки з індексацією: якщо значення є у колонці Чоловіки.1, це чоловік (m), якщо у Жінки.1 — жінка (f).

Після цього:

  • Витягуємо вік та стать
  • Перетворюємо вік у числовий формат
  • Формуємо фінальну таблицю з віком, статтю та роком 1778
In [27]:
def detect_gender(row):
    if pd.notna(row['Чоловіки.1']):
        return 'm'
    elif pd.notna(row['Жінки.1']):
        return 'f'
    else:
        return None
        
df_1778['Стать'] = df_1778.apply(detect_gender, axis=1)

df_1778['Вік'] = pd.to_numeric(df_1778['Вік'], errors='coerce')

df_1778_clean = df_1778[['Стать', 'Вік']].dropna()
df_1778_clean['year'] = 1778

df_1778_clean.head()
Out[27]:
Стать Вік year
0 m 44 1778
1 f 35 1778
2 f 17 1778
3 m 15 1778
4 m 14 1778
Побудова порівняльної гістограми вікової структури (1778 vs 1897)¶

У цьому блоці:

  • Об'єднуються оброблені таблиці за 1778 і 1897 рік
  • Фільтруються лише ті записи, де вік від 0 до 100 років (але там і значення не виходять за 100)
  • Дані розділяються за роками для побудови окремих графіків

Потім будується дві гістограми:

  • Ліва - вікова структура села Дунаєць у 1778 році
  • Права - вікова структура у 1897 році

Використано matplotlib, а для осі X встановлено крок 10 років для зручності читання

Примітка: значення, де вказано вік у місяцях взято як 0 для зручності оформлення графіку

In [28]:
df_all_age = pd.concat([df_1897, df_1778_clean])
df_filtered = df_all_age[(df_all_age['Вік'] >= 0) & (df_all_age['Вік'] <= 100)]

df_1778_plot = df_filtered[df_filtered['year'] == 1778]
df_1897_plot = df_filtered[df_filtered['year'] == 1897]

bins = np.arange(0, 101, 5)

fig, axes = plt.subplots(ncols=2, figsize=(14, 5), sharey=False)

# ------ 1778 ------
axes[0].hist(df_1778_plot['Вік'], bins=bins, color=COLORS[1778], edgecolor='#FE2F41', alpha=0.6)
axes[0].set_title('Рік 1778')
axes[0].set_xlabel('Вік')
axes[0].set_ylabel('Кількість осіб')
axes[0].set_xticks(np.arange(0, 101, 10))

# ------ 1897 ------
axes[1].hist(df_1897_plot['Вік'], bins=bins, color=COLORS[1897], edgecolor='#3F7CF9', alpha=0.6)
axes[1].set_title('Рік 1897')
axes[1].set_xlabel('Вік')
axes[1].set_ylabel('Кількість осіб')
axes[1].set_xticks(np.arange(0, 101, 10))

fig.suptitle('Порівняння вікової структури села Дунаєць: 1778 vs 1897', fontsize=14)

plt.tight_layout()
plt.show()
No description has been provided for this image

На цьому графіку зображено, як змінювався віковий розподіл жителів села Дунаєць протягом понад 100 років

Зокрема, можна побачити, що:

  • у 1778 році населення мало чітко виражену перевагу людей віком до 25 років
  • у 1897 році вікова структура стала більш "розтягнутою", із більшою часткою дорослих і людей літнього віку
  • Тобто можна зробити висновок, що у 1897 році люди почали жити довше

Підтема 2: Порівняння частки вікових груп населення¶

Для кращого аналізу даних зручно об’єднати людей у вікові групи, це дозволяє побачити, наскільки різнилася структура суспільства за групами - дітей, дорослих, літніх людей

Створення категорій вікових груп¶

У цій комірці задається функція assign_age_group, яка класифікує населення за віковими групами:

  • 0–12 років - діти
  • 13–18 років - підлітки
  • 19–59 років - дорослі
  • 60+ років - літні люди
In [29]:
# Створення вікових категорій:
def assign_age_group(age):
    if age < 13:
        return '0–12 (діти)'
    elif age < 19:
        return '13–18 (підлітки)'
    elif age < 60:
        return '19–59 (дорослі)'
    else:
        return '60+ (літні)'
Групування населення за віковими категоріями і підрахунок часток¶

Цей блок:

  • застосовує функцію вікової класифікації до всіх записів
  • рахує кількість осіб у кожній віковій групі окремо для 1778 та 1897 року
  • обчислює відсоткову частку кожної групи в межах відповідного року
  • впорядковує вікові групи в логічному порядку для зручної візуалізції
In [30]:
df_groups = df_filtered.copy()
df_groups['age_group'] = df_groups['Вік'].apply(assign_age_group)

# Агрегація
group_counts = df_groups.groupby(['year', 'age_group']).size().reset_index(name='count')
group_totals = df_groups.groupby('year').size().reset_index(name='total')
group_data = pd.merge(group_counts, group_totals, on='year')

group_data['percent'] = round(
    group_data['count'] / group_data['total'] * 100, 1
)

# Сортування груп
age_order = ['0–12 (діти)', '13–18 (підлітки)', '19–59 (дорослі)', '60+ (літні)']

group_data['age_group'] = pd.Categorical(
    group_data['age_group'], 
    categories=age_order,
    ordered=True
)

group_data = group_data.sort_values(['year', 'age_group'])
Побудова порівняльної діаграми частки вікових груп¶

Цей графік відображає, як змінилася частка різних вікових груп у населенні села Дунаєць за понад 100 років - між 1778 і 1897 роками.

Зокрема, видно що:

  • наскільки зменшилась чи зросла частка дітей і підлітків
  • як змінилася частка дорослого працездатного населення
  • чи зросла кількість літніх людей
In [31]:
years = sorted(group_data['year'].unique())
groups = ['0–12 (діти)', '13–18 (підлітки)', '19–59 (дорослі)', '60+ (літні)']

width = 0.35
x = np.arange(len(groups))

values_1778 = group_data[group_data['year'] == 1778].set_index('age_group').loc[groups]['percent']
values_1897 = group_data[group_data['year'] == 1897].set_index('age_group').loc[groups]['percent']

fig, ax = plt.subplots(figsize=(9, 5))

bar1 = ax.bar(x - width/2, values_1778, width, label='1778', color=COLORS[1778], edgecolor='grey', alpha=0.8)
bar2 = ax.bar(x + width/2, values_1897, width, label='1897', color=COLORS[1897], edgecolor='grey', alpha=0.8)

ax.set_title('Частка вікових груп населення села Дунаєць')
ax.set_xlabel('Вікова група')
ax.set_ylabel('Частка населення, %')
ax.set_xticks(x)
ax.set_xticklabels(groups)
ax.set_ylim(0, 100)
ax.legend(title='Рік')
ax.grid(axis='y', linestyle='--', alpha=0.5)

plt.tight_layout()
plt.show()
No description has been provided for this image

Підтема 3: Середній вік осіб у родинних ролях у 1778 та 1897 роках¶

Ця підтема досліджує, як змінювався середній вік людей залежно від їхньої ролі в родині: господар, жінка, діти

Це дозволяє краще зрозуміти, в якому віці люди вступали в шлюб, ставали "господарями" та скільки років мали їхні діти

Завантаження та обробка даних (1778 і 1897 роки)¶

У цій комірці відбувається завантаження даних з переписів населення 1778 та 1897 років

Для кожного року:

  • обираємо лише необхідні колонки (родинний статус та вік)
  • перетворюємо вік на числовий тип
  • додаємо колонку з роком
  • перейменовуємо ''господар'' у 1778 році як ''husband'' для подальшого об'єднання з 1897 роком
In [32]:
# ------ 1778 ------
df_1778 = pd.read_csv("2.2_Дунаєць_1778.xlsx - База.csv", skiprows=3)
df_1778 = df_1778[['Родиний статус', 'Вік']].dropna()
df_1778['Вік'] = pd.to_numeric(df_1778['Вік'], errors='coerce')
df_1778 = df_1778.dropna()
df_1778.columns = ['роль', 'вік']
df_1778['рік'] = 1778

# Перейменування ролей
df_1778['роль'] = df_1778['роль'].replace({'господар': 'husband'})

# ------ 1897 ------
df_1897 = pd.read_csv('2.1_Дунаєць.xlsx - База людей.csv')
df_1897 = df_1897[['Глава хозяйства и глава семьи', 'Возраст']].dropna()
df_1897['Возраст'] = pd.to_numeric(df_1897['Возраст'], errors='coerce')
df_1897 = df_1897.dropna()
df_1897.columns = ['роль', 'вік']
df_1897['рік'] = 1897

df_1897.head()
Out[32]:
роль вік рік
0 husband 32.0 1897
1 wife 28.0 1897
2 daughter 5.0 1897
3 son 1.0 1897
4 father 70.0 1897
Уніфікація родинних ролей, обʼєднання таблиць та обчислення середнього віку¶

Цей блок:

  • об'єднує ролі, які могли бути записані по-різному (наприклад, 'жена его' -> 'wife')
  • застосовує однакові позначення ролей у таблицях 1778 і 1897 років
  • об'єднує дані обох переписів
  • обирає тільки ключові ролі для аналізу ('husband', 'wife', 'son', 'daughter')
  • обчислює середній вік для кожної ролі у кожному з років
  • формує зведену таблицю для побудови графіка
In [33]:
# Перейменування ролей 1897
role_mapping = {
    'господар': 'husband',
    'жена его': 'wife',
    'дочь их': 'daughter',
    'сын их': 'son',
    'husband': 'husband',
    'father': 'husband'
}

df_1778['роль'] = df_1778['роль'].replace(role_mapping)
df_1897['роль'] = df_1897['роль'].replace(role_mapping)

df_roles = pd.concat([df_1778, df_1897])
top_roles = ['wife', 'husband', 'son', 'daughter']
df_roles_filtered = df_roles[df_roles['роль'].isin(top_roles)]

mean_ages = df_roles_filtered.groupby(['рік', 'роль'])['вік'].mean().reset_index()
mean_ages_pivot = mean_ages.pivot(
    index='роль', 
    columns='рік', 
    values='вік'
).round(1).fillna(0)

mean_ages_pivot
Out[33]:
рік 1778 1897
роль
daughter 8.4 11.0
husband 47.1 46.6
son 12.2 16.4
wife 37.0 41.1
Побудова графіка: середній вік у родинних ролях¶

У цій частині будується стовпчиковий графік, який порівнює середній вік у типових родинних ролях між 1778 та 1897 роками

Це дозволяє візуально побачити зміну соціальної структури сімʼї, наприклад:

  • Чи став husband молодшим, а його дружина старшою... чи навпаки?
  • Чи змінився вік дочок або синів?
In [34]:
roles = mean_ages_pivot.index.tolist()
x = np.arange(len(roles))
width = 0.35

fig, ax = plt.subplots(figsize=(10, 6))

bars_1778 = ax.bar(
    x - width/2,
    mean_ages_pivot[1778],
    width, label='1778',
    color=COLORS[1778],
    edgecolor='#89918A',
    alpha=0.8
)

bars_1897 = ax.bar(
    x + width/2,
    mean_ages_pivot[1897],
    width, label='1897',
    color=COLORS[1897],
    edgecolor='#89918A',
    alpha=0.8
)

for bar in bars_1778:
    height = bar.get_height()
    ax.text(bar.get_x() + bar.get_width()/2, height + 0.8, f'{height}', ha='center', va='bottom', fontsize=9)

for bar in bars_1897:
    height = bar.get_height()
    ax.text(bar.get_x() + bar.get_width()/2, height + 0.8, f'{height}', ha='center', va='bottom', fontsize=9)

ax.set_title('Середній вік у родинних ролях у 1778 і 1897 роках')
ax.set_xlabel('Родинна роль')
ax.set_ylabel('Середній вік, років')
ax.set_xticks(x)
ax.set_xticklabels(roles)
ax.set_ylim(0, max(mean_ages_pivot.max()) + 10)
ax.legend(title='Рік')
ax.grid(axis='y', linestyle='--', alpha=0.5)

plt.tight_layout()
plt.show()
No description has been provided for this image
Висновок до графіка¶

Цей графік демонструє середній вік осіб у різних родинних ролях у двох роках

Загалом вікові показники змінилися помірно. Найбільше зростання спостерігається серед жінок та дітей, тоді як вік чоловіків-господарів залишився практично без змін. Це може свідчити про поступові соціальні зрушення у віці вступу в ключові родинні ролі

ТЕМА 2: Зміни у структурі родин у селі Дунаєць між 1778 та 1897 роками¶

У цьому розділі ми аналізуємо, як змінилася структура родин у селі Дунаєць, порівнюючи дані сповідного розпису 1778 року та всеросійського перепису населення 1897 року

Метою є визначити, які типи родин були характерні для обох періодів, як змінювалася вага ядерних, розширених, мультифокальних та інших типів домогосподарств, і що це може означати з точки зору демографічних та соціальних процесів

Для цієї теми нам знадоблять такі таблиці, тому завантажимо їх

  • Загальні таблиці (Total.csv) — містять сумарну інформацію про кількість родин різних типів
  • Таблиці по соціальних станах (files_1778, files_1897) - деталізовані дані по кожному стану, які дозволяють порівняти типи родин всередині конкретних соціальних груп
In [35]:
df_1778 = pd.read_csv('2.2.2_Структура родини Дунаєць_1778.xlsx - Total.csv')
df_1897 = pd.read_csv('2.1.1_Структура родини Дунаєць_1897.xlsx - Total.csv')

files_1897 = {
    'Козаки': '2.1.1_Структура родини Дунаєць_1897.xlsx - Cossacks.csv',
    'Селяни-власники': '2.1.1_Структура родини Дунаєць_1897.xlsx - Peasant-owner.csv',
    'Духовенство': '2.1.1_Структура родини Дунаєць_1897.xlsx - Dukhovnogo.csv'
}

files_1778 = {
    'Посполиті': '2.2.2_Структура родини Дунаєць_1778.xlsx - Посполитые+бездворные.csv',
    'Військові': '2.2.2_Структура родини Дунаєць_1778.xlsx - Военные.csv',
    'Духовенство': '2.2.2_Структура родини Дунаєць_1778.xlsx - Духовные.csv'
}

Підтема 1: Аналіз змін у структурі родин у селі Дунаєць між 1778 та 1897 роками¶

У цьому дослідженні ми аналізуємо, як змінилася структура родин у селі Дунаєць між 1778 роком (сповідний розпис) та 1897 роком (перепис населення)

Нас цікавить розподіл родин за основними типами:

  • Самотні особи
  • Безструктурні
  • Нуклеарні
  • Розширені
  • Мультифокальні

Потрібно оцінити, як змінилося їхнє розподілення в загальній структурі родин

Очищення та підготовка даних¶
  • Заповнюємо порожні значення у колонці Категорія методом forward fill (бо вони зазначені лише на перших рядках блоку)
  • Видаляємо службовий рядок "Усього", який є підсумком
  • Групуємо по категоріях і обчислюємо суму родин
  • Розраховуємо відсоткову частку кожної категорії в межах відповідного року
In [36]:
df_1778['Категорія'] = df_1778['Категорія'].ffill()
df_1897['Категорія'] = df_1897['Категорія'].ffill()

df_1778 = df_1778[df_1778['Категорія'].str.lower() != 'усього']
df_1897 = df_1897[df_1897['Категорія'].str.lower() != 'усього']

# Групування по категоріях та підрахунок загальної кількості родин
df_1778_grouped = df_1778.groupby('Категорія')['Кількість'].sum().reset_index()
df_1897_grouped = df_1897.groupby('Категорія')['Кількість'].sum().reset_index()

# Розрахунок відсотків для кожної категорії
df_1778_grouped['%'] = (df_1778_grouped['Кількість'] / df_1778_grouped['Кількість'].sum()) * 100
df_1897_grouped['%'] = (df_1897_grouped['Кількість'] / df_1897_grouped['Кількість'].sum()) * 100
Побудова графіків¶

Щоб порівняння було візуально зрозумілим, ми фіксуємо однаковий порядок категорій і кольори для обох графіків

На наступних графіках зображено частки кожного типу родини у загальній структурі села Дунаєць у 1778 та 1897 роках

Кругові діаграми обрані для наочного показу відсоткової структури родин за категоріями

Функція plot_pie

Функція будує кругову діаграму для заданого року. Вона:

  • фільтрує нульові категорії
  • впорядковує категорії за заданим порядком (category_order)
  • призначає кольори (category_colors)
  • додає підписи та легенду
In [37]:
category_order = [
    'Самотні особи',
    'Безструктурні',
    'Нуклеарні',
    'Розширені',
    'Мультифокальні'
]

category_colors = {
    'Самотні особи': '#92C5F9',
    'Безструктурні': '#FFE072',
    'Нуклеарні': '#B6A6E9',
    'Розширені': '#AFDC8F',
    'Мультифокальні': '#F89B78'
}

# Функція побудови кругової діаграми
def plot_pie(df, year, ax):
    filtered = df[df['%'] > 0].copy()
    filtered = filtered.set_index('Категорія').reindex(category_order).dropna().reset_index()
    
    colors = [category_colors[cat] for cat in filtered['Категорія']]

    wedges, texts, autotexts = ax.pie(
        filtered['%'],
        labels=filtered['Категорія'],
        autopct='%1.1f%%',
        startangle=140,
        colors=colors,
        textprops={'fontsize': 10}
    )
    ax.set_title(f'Розподіл родин за категоріями, {year}', fontsize=13)
    ax.legend(wedges, filtered['Категорія'], title='Категорії', loc='center left', bbox_to_anchor=(1, 0.6))

fig, axes = plt.subplots(1, 2, figsize=(16, 8))
plot_pie(df_1778_grouped, 1778, axes[0])
plot_pie(df_1897_grouped, 1897, axes[1])

plt.tight_layout()
plt.show()
No description has been provided for this image
Висновки¶
  1. Мультифокальні родини (великі розгалужені родини) були дуже поширені у 1778 році — майже 80% усіх родин. Але до 1897 року їх залишилось тільки 34%. Це свідчить про те, що великі родини поступово зникали

  2. Нуклеарні родини (лише батьки та діти) навпаки стали основним типом — зросли з 19% до 44%. Це показує, що за 100 років сімейна структура стала простішою і більш "сучасною"

  3. У 1897 році з’явилися самотні особи — близько 3% людей жили окремо. У 1778 році таких не виділяли окремо. Це може бути наслідком таких явищ, як старіння населення, смерть членів родини або еміграція

  4. Розширені родини (де живуть, наприклад, батьки з дітьми та бабусею/дідусем) також стали частішими — з 1.8% до 20%. Це показує, що частина людей все ще жила у міжпоколінних родинах


Підтема 2: Аналіз змін у структурі родин у селі Дунаєць між 1778 та 1897 роками¶

Ця підтема показує порівняння структури родин (типів родин) у різних соціальних станах села Дунаєць у 1778 та 1897 роках. На кожному графіку:

  • показано 5 основних типів родин (самотні, безструктурні, нуклеарні, розширені, мультифокальні)
  • висота стовпчика показує, яку частку родин певного типу становили в межах кожного стану

Це дає змогу побачити, як родинні моделі пов’язані зі станом і як вони змінювались у часі

Аналіз¶

Ця частина коду завантажує дані про структуру родин у 1778 році для кожного соціального стану

Порожні значення у колонці "Категорія" заповнюються згори (ffill()), рядки "усього" видаляються, а далі всі таблиці об’єднуються в один датафрейм

Ми підраховуємо кількість родин кожного типу в межах кожного стану, а також розраховуємо частку кожного типу родини у відсотках

Колонка 'Рік' додається, щоб потім об’єднати з даними за 1897 рік.

In [38]:
def load_family_structure(path, stan_label):
    df = pd.read_csv(path)
    df['Стан'] = stan_label
    df['Категорія'] = df['Категорія'].ffill()
    df = df[df['Категорія'].str.lower() != 'усього']
    
    return df[['Стан', 'Категорія', 'Кількість']]

df_1778_parts = [load_family_structure(path, stan) for stan, path in files_1778.items()]
df_1778_combined = pd.concat(df_1778_parts, ignore_index=True)

# Агрегація: кількість родин по типах у межах кожного стану
df_1778_summary = df_1778_combined.groupby(['Стан', 'Категорія'])['Кількість'].sum().reset_index()

stan_totals = df_1778_summary.groupby('Стан')['Кількість'].transform('sum')
df_1778_summary['%'] = (df_1778_summary['Кількість'] / stan_totals) * 100

# Додаємо колонку 'Рік' для подальшого обʼєднання з 1897
df_1778_summary['Рік'] = 1778

df_1778_summary.head()
Out[38]:
Стан Категорія Кількість % Рік
0 Військові Безструктурні 0 0.000000 1778
1 Військові Мультифокальні 33 94.285714 1778
2 Військові Нуклеарні 2 5.714286 1778
3 Військові Розширені 0 0.000000 1778
4 Військові Самотні особи 0 0.000000 1778

Тут аналогічно обробляються дані за 1897 рік. Для кожного стану зчитується таблиця, заповнюються пропущені категорії, фільтруються службові рядки

Після обʼєднання всіх даних ми рахуємо, який відсоток родин кожного типу був у межах кожного соціального стану

Це дозволяє порівнювати розподіли родин по типах і соціальних групах у двох різних роках

In [39]:
df_1897_parts = [load_family_structure(path, stan) for stan, path in files_1897.items()]
df_1897_combined = pd.concat(df_1897_parts, ignore_index=True)

df_1897_summary = df_1897_combined.groupby(['Стан', 'Категорія'])['Кількість'].sum().reset_index()

# Обчислюємо суму 'Кількість' для кожного стану
stan_totals = df_1897_summary.groupby('Стан')['Кількість'].transform('sum')

df_1897_summary['%'] = (df_1897_summary['Кількість'] / stan_totals) * 100

df_1897_summary['Рік'] = 1897

df_1897_summary.head()
Out[39]:
Стан Категорія Кількість % Рік
0 Духовенство Безструктурні 0 0.0 1897
1 Духовенство Мультифокальні 0 0.0 1897
2 Духовенство Нуклеарні 1 50.0 1897
3 Духовенство Розширені 1 50.0 1897
4 Духовенство Самотні особи 0 0.0 1897
Підготовка даних для побудови графіка¶

У цьому блоці ми:

  • задали порядок категорій (category_order), щоб типи родин завжди йшли в одному порядку на всіх графіках
  • визначили кольори для років у словнику year_colors
  • відібрали лише ті типи родин, які нас цікавлять (фільтрація через .isin(...))
  • виділили список унікальних станів через .unique() - далі будемо малювати по одному графіку на кожен стан
In [40]:
category_order = ['Самотні особи', 'Безструктурні', 'Нуклеарні', 'Розширені', 'Мультифокальні']

year_colors = {
    1778: '#FFE072',
    1897: '#92C5F9'
}

df_combined_social_family = pd.concat([df_1778_summary, df_1897_summary], ignore_index=True)
df = df_combined_social_family[df_combined_social_family['Категорія'].isin(category_order)]

# Унікальні стани
unique_stans = df['Стан'].unique()
num_stans = len(unique_stans)
Побудова графіків для кожного стану¶

Цей код будує набір стовпчикових графіків - один графік для кожного соціального стану

Ми використовуємо plt.subplots() для створення сітки графіків, де для кожного стану:

  • для кожного типу родини виводимо два стовпці (1778 зліва, 1897 справа)
  • якщо немає даних автоматично показуємо 0;
  • якщо кількість станів не парна - зайві пусті віконця (subplot) видаляються через fig.delaxes(...).
In [41]:
fig, axes = plt.subplots(2, (num_stans + 1) // 2, figsize=(14, 5 * (num_stans + 1) // 2))

axes = axes.flatten()

for i, stan in enumerate(unique_stans):
    ax = axes[i]
    data = df[df['Стан'] == stan]
    x = np.arange(len(category_order))
    width = 0.35

    # Значення для 1778 року: якщо категорія відсутня — підставляємо 0
    y_1778 = [
        data[(data['Категорія'] == cat) & (data['Рік'] == 1778)]['%'].values[0]
        if not data[(data['Категорія'] == cat) & (data['Рік'] == 1778)].empty else 0
        for cat in category_order
    ]
    
    # Значення для 1897 року (аналогічно)
    y_1897 = [
        data[(data['Категорія'] == cat) & (data['Рік'] == 1897)]['%'].values[0]
        if not data[(data['Категорія'] == cat) & (data['Рік'] == 1897)].empty else 0
        for cat in category_order
    ]

    bar_1778 = ax.bar(x - width/2, y_1778, width, color=year_colors[1778], label='1778')
    bar_1897 = ax.bar(x + width/2, y_1897, width, color=year_colors[1897], label='1897')


    ax.set_title(f'Стан: {stan}')
    ax.set_ylabel('% частка')
    ax.set_xticks(x)
    ax.set_xticklabels(category_order, rotation=30)
    ax.set_ylim(0, 100)

# Видаляємо зайві subplotи, якщо кількість станів непарна
for j in range(i + 1, len(axes)):
    fig.delaxes(axes[j])

# Додаємо загальну легенду для обох років (на основі одного з барів)
fig.legend(
    handles=[bar_1778[0], bar_1897[0]],
    labels=['1778', '1897'],
    title='Рік',
    loc='upper center',
    ncol=2,
    bbox_to_anchor=(1, 1.04)
)

fig.suptitle('Типи родин за станами у 1778 та 1897 роках', fontsize=16, y=1.02)

plt.tight_layout()
plt.show()
No description has been provided for this image

Висновки: як змінювалася структура родин у різних станах¶

У 1778 році найбільше було мультифокальних родин — особливо у військових та посполитих. Це були великі, часто багатопоколінні родини

До 1897 року ситуація змінилась: у більшості станів зросла частка нуклеарних родин (мама, тато, діти). Наприклад, у селян і козаків понад 40% родин стали нуклеарними

У духовенства зʼявилися також розширені родини. А от мультифокальні родини стали менш поширеними у всіх групах.

В цілому, бачимо перехід від великих традиційних родин до менших і більш "сучасних" форм, але з різними темпами у кожному стані


ТЕМА 3: Зміни у статево-віковій структурі населення села Дунаєць у 1778 та 1897 роках¶

Ця тема дозволяє дослідити, як змінилося співвідношення чоловіків та жінок у різних вікових групах за понад 100 років. Це важливо для розуміння демографічних зрушень, соціальної структури, тривалості життя та загального становища населення

Завантажуємо дані для цієї теми - нам знадобляться ці дві таблиці

In [42]:
df_people_1778 = pd.read_csv("2.2_Дунаєць_1778.xlsx - База.csv", skiprows=3)
df_people_1897 = pd.read_csv("2.1_Дунаєць.xlsx - База людей.csv")

Також на дві підтеми у нас будуть використні спільні дані - це вікові категорії, тому зробимо відразу очистку даних

Очистка даних¶

У цьому блоці ми виділили тільки потрібні нам змінні — вік та стать

Для 1778 року стать визначається за наявністю запису у колонці "Чоловіки" або "Жінки", для 1897 — зі стовпчика "Пол". Також перетворюємо вік на числовий формат та прибираємо пропущені значення

In [43]:
# Очистка 1778 року
df_1778 = df_people_1778.copy()
df_1778['Стать'] = None
df_1778.loc[df_1778['Чоловіки'].notna(), 'Стать'] = 'm'
df_1778.loc[df_1778['Жінки'].notna(), 'Стать'] = 'f'
df_1778_demo = df_1778[['Стать', 'Вік']].dropna()
df_1778_demo['Вік'] = pd.to_numeric(df_1778_demo['Вік'], errors='coerce')
df_1778_demo = df_1778_demo.dropna()

# Очистка 1897 року
df_1897_demo = df_people_1897[['Пол', 'Возраст']].rename(columns={'Пол': 'Стать', 'Возраст': 'Вік'})
df_1897_demo['Вік'] = pd.to_numeric(df_1897_demo['Вік'], errors='coerce')
df_1897_demo = df_1897_demo.dropna()

Обробка даних¶

Визначаємо вікові групи по 10 років: 0–9, 10–19, ..., до 90–99. Це зручно для побудови демографічної пірамі та графіка коефіцієнта статі

Потім агрегуємо дані по групах і статі - для кожного року створюється таблиця з кількістю чоловіків і жінок у кожній групі

In [44]:
# Функція створення вікових груп
def assign_age_group(age):
    if age < 10:
        return '0-9'
    elif age < 20:
        return '10-19'
    elif age < 30:
        return '20-29'
    elif age < 40:
        return '30-39'
    elif age < 50:
        return '40-49'
    elif age < 60:
        return '50-59'
    elif age < 70:
        return '60-69'
    elif age < 80:
        return '70-79'
    elif age < 90:
        return '80-89'
    else:
        return '90-99'

df_1778_demo['Вікова група'] = df_1778_demo['Вік'].apply(assign_age_group)
df_1897_demo['Вікова група'] = df_1897_demo['Вік'].apply(assign_age_group)

Підтема 1: Порівняння демографічних пірамід 1778 та 1897 років¶

Опис підтеми¶

У цій підтемі ми порівнюємо статево-вікову структуру населення села Дунаєць у вигляді демографічних пірамід. Це дозволяє візуально оцінити:

  • співвідношення чоловіків і жінок у кожній віковій групі
  • зміни в кількості дітей, дорослих і літніх людей між 1778 та 1897 роками
Обробка даних¶

У цьому блоці створено функцію make_pyramid_data, яка:

  • групує записи за віковими категоріями та статю
  • рахує кількість осіб у кожній комбінації
In [45]:
def make_pyramid_data(df):
    return df.groupby(['Вікова група', 'Стать']).size().unstack(fill_value=0)

pyramid_1778 = make_pyramid_data(df_1778_demo)
pyramid_1897 = make_pyramid_data(df_1897_demo)
Візуалізація¶

Цей код будує два графіки - демографічні піраміди для 1778 та 1897 років

Зліва - кількість чоловіків (від'ємне значення), справа — жінки

Графіки мають спільну вісь вікових груп і однаковий масштаб, щоб зручно порівнювати

Цей підхід дозволяє швидко побачити, чи було переважання чоловіків або жінок у певних вікових категоріях, а також які групи населення зменшилися або зросли з часом

In [46]:
age_order = ['0-9', '10-19', '20-29', '30-39', '40-49', '50-59', '60-69', '70-79', '80-89', '90-99']
fig, axes = plt.subplots(1, 2, figsize=(14, 7), sharey=True)

for ax, (year, data) in zip(axes, [(1778, pyramid_1778), (1897, pyramid_1897)]):
    data = data.reindex(age_order).fillna(0)
    males = -data['m']
    females = data['f']

    ax.barh(age_order, males, color='#6795F2', label='Чоловіки', alpha=0.8)
    ax.barh(age_order, females, color='#F56C78', label='Жінки', alpha=0.8)
    ax.grid(True, axis='x', linestyle='--', alpha=0.7)
    ax.set_title(f'Статева структура {year}')
    ax.set_xlabel('Кількість')
    ax.set_ylabel('Вікова група')
    ax.set_xlim(-200, 200) 
    ax.legend()

plt.tight_layout()
plt.show()
No description has been provided for this image
Висновок¶

У 1778 році більшість населення була зосереджена у молодших вікових групах, що типово для доіндустріального суспільства

У 1897 році структура стала трохи “старшою” - зростає частка людей у середніх вікових групах, хоча дітей також багато

Чоловіків і жінок приблизно порівну, але у 1778 році видно трохи більшу частку хлопців серед наймолодших


Підтема 2: Вікова та статева структура у селі Дунаєць 1778 та 1897 років¶

Опис підтеми¶

Ця підтема дозволяє глибше проаналізувати не лише загальну чисельність населення, але й баланс між чоловіками та жінками у різних вікових групах, що може вказувати на соціальні, економічні або демографічні особливості життя в той чи інший період

Обробка даних¶

У цьому блоці:

  • створено функцію compute_sex_ratio(), яка обчислює коефіцієнт співвідношення статей у кожній віковій групі
  • значення подається як кількість чоловіків на 100 жінок
  • окремо розраховано коефіцієнт для 1778 та 1897 років
In [47]:
# Обчислення коефіцієнта статі
def compute_sex_ratio(df):
    grouped = df.groupby(['Вікова група', 'Стать']).size().unstack(fill_value=0)
    grouped['Коефіцієнт'] = (grouped['m'] / grouped['f']) * 100
    return grouped['Коефіцієнт']

sex_ratio_1778 = compute_sex_ratio(df_1778_demo)
sex_ratio_1897 = compute_sex_ratio(df_1897_demo)
Візуалізація даних¶

На графіку побудовано дві лінії:

  • По осі X — вікові групи (по 10 років), по осі Y — кількість чоловіків на 100 жінок
  • Сіра горизонтальна лінія на рівні 100 вказує на статевий баланс, тобто однакова кількість чоловіків і жінок.

Цей тип графіка дозволяє одразу візуально оцінити, у яких вікових групах переважали чоловіки, а у яких — жінки, і як це змінилося за сто років

In [48]:
age_order = ['0-9', '10-19', '20-29', '30-39', '40-49', '50-59', '60-69', '70-79', '80-89', '90-99']

plt.figure(figsize=(10, 6))
plt.plot(age_order, sex_ratio_1778.reindex(age_order), marker='o', label='1778', color='#6795F2', alpha=0.8)
plt.plot(age_order, sex_ratio_1897.reindex(age_order), marker='o', label='1897', color='#F56C78', alpha=0.8)
plt.axhline(100, color='gray', linestyle='--', linewidth=1)

plt.title('Коефіцієнт статі (чоловіків на 100 жінок) у вікових групах')
plt.ylabel('Коефіцієнт (ч на 100 ж)')
plt.xlabel('Вікова група')
plt.ylim(0, 250)
plt.grid(True, linestyle='--', alpha=0.5)
plt.legend()
plt.tight_layout()
plt.show()
No description has been provided for this image
Опис графіка та виснокви¶
  • Лінія на рівні 100 означає співвідношення 1:1
  • Якщо показник >100 – чоловіків більше, якщо <100 – жінок більше в цій віковій групі

Видно, що:

  • У молодших групах (0-9, 10-19) переважають хлопці
  • У старших групах частіше залишаються жінки (особливо у 1897)
  • Це показує класичну демографічну картину: чоловіки мають вищу смертність з віком, а жінки живучіші

Підтема 3: Середній вік чоловіків і жінок у 1778 та 1897¶

Опис підтеми¶

У цій підтемі досліджується, як змінився середній вік населення за статтю — окремо для чоловіків і жінок у 1778 та 1897 роках Це дозволяє зробити припущення про тривалість життя, шлюбний вік і загальний демографічний розвиток.

Обробка даних¶

У цьому блоці:

  • середній вік обчислюється окремо для чоловіків і жінок у кожному з двох років
  • створюється загальна таблиця mean_age_df для подальшої візуалізації
In [49]:
# Обчислення середнього віку по статі
mean_age_1778 = df_1778_demo.groupby('Стать')['Вік'].mean()
mean_age_1897 = df_1897_demo.groupby('Стать')['Вік'].mean()

# Створення загальної таблиці
mean_age_df = pd.DataFrame({
    '1778': mean_age_1778,
    '1897': mean_age_1897
}).T
Візуалізація даних¶

На графіку зображено стовпчики для:

  • 1778 року — середній вік чоловіків і жінок майже однаковий: 23.5 та 23.6;
  • 1897 року — середній вік зріс у обох груп, але у жінок більше (27.7 проти 25.2)
In [50]:
fig, ax = plt.subplots(figsize=(8, 5))
bars = mean_age_df.plot(kind='bar', ax=ax, color=['#6795F2', '#F56C78'], alpha=0.7)

# Підписи над кожним стовпчиком
for container in bars.containers:
    bars.bar_label(container, fmt='%.1f', label_type='edge', fontsize=10)

ax.set_title('Середній вік чоловіків і жінок у 1778 та 1897 роках')
ax.set_ylabel('Середній вік')
ax.set_xlabel('Рік')
ax.set_xticks([0, 1])
ax.set_xticklabels(['1778', '1897'])
ax.grid(axis='y', linestyle='--', alpha=0.6)
ax.legend(title='Стать', labels=['Чоловіки', 'Жінки'])

plt.tight_layout()
plt.show()
No description has been provided for this image
Висновки:¶
  • Середній вік як чоловіків, так і жінок зріс з 1778 по 1897 рік — приблизно на 2 роки в чоловіків і понад 4 роки у жінок
  • У 1778 році середній вік обох статей був майже однаковим, а в 1897 році жінки стали в середньому старшими за чоловіків

Ці зміни можуть бути пов’язані зі зростанням тривалості життя, покращенням умов побуту, або тим, що більше жінок доживало до старшого віку

In [ ]: